package com.albermark.photoorganizer.processor;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.springframework.util.Assert;

import com.albermark.photoorganizer.pojo.Photo;

/**
 * 
 * @author Nuno Ferreira
 *
 */
public class PhotoProcessor extends Thread {
	
	private static Logger LOG = Logger.getLogger(PhotoProcessor.class);
	
	private int processCounter = 0;

	private final File targetRootDirectory;
	private final BlockingQueue<Photo> photosQueue;
	
	private List<Photo> failedPhotos = new ArrayList<Photo>();

	private final boolean deletePhotoAfterCopy;
	
	public PhotoProcessor(String processName, File targetRootDirectory, BlockingQueue<Photo> photosQueue, boolean deletePhotoAfterCopy) {
		
		Assert.notNull(targetRootDirectory);
		Assert.isTrue(targetRootDirectory.exists());
		Assert.isTrue(targetRootDirectory.isDirectory());
		Assert.notNull(photosQueue);
		
		setName(processName);
		this.deletePhotoAfterCopy = deletePhotoAfterCopy;
		this.targetRootDirectory = targetRootDirectory;
		this.photosQueue = photosQueue;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void run() {
		try {
			
			Photo photo = null;
			
			while (isAlive()) {
				
				photo = photosQueue.poll(3, TimeUnit.SECONDS);
				
				if (photo != null) {
					
					process(photo);	
				}
			}		
		} catch (InterruptedException e) {
			e.printStackTrace();
		} 
		
		for (Photo failedPhoto : failedPhotos) {
			process(failedPhoto);
		}
	}
	
	protected void process(Photo photo) {
		
		logQueueInfo();
		
		try {
			File originalFile = photo.getOriginalFile();
			File targetFile = getTargetFile(photo);

			if (targetFile == null) {
				
				if (deletePhotoAfterCopy) {
					deleteFile(originalFile);
				}
				return;
			}
			
			FileUtils.copyFile(originalFile, targetFile, true);
			
			long originalChecksum = FileUtils.checksumCRC32(originalFile);
			long targetChecksum = FileUtils.checksumCRC32(targetFile);

			boolean checksumTest = originalChecksum == targetChecksum;
			
			if (!checksumTest) {
				LOG.warn("Checksums not matching! originalChecksum: " + originalChecksum + "\ntargetChecksum:   " + targetChecksum);	
			} else {
				if (deletePhotoAfterCopy) {
					deleteFile(originalFile);
				}
			}

			
		} catch (Exception e) {
			
			if (!failedPhotos.contains(photo)) {
				failedPhotos.add(photo);
			} else {
				LOG.error("Error processing photo: " + photo.toString());
				e.printStackTrace();
			}
		}
	}
	
	private void deleteFile(File file) {
		Assert.notNull(file);
		
		if (file.delete()) {
			LOG.info("File '" + file.getAbsolutePath() + "' deleted.");
		} else {
			LOG.warn("Could not delete file '" + file.getAbsolutePath() + "'.");
		}
	}
	
	private void logQueueInfo() {
		processCounter++;
		
		if (processCounter % 10 == 0) {
			LOG.info(processCounter + " files processed. Queue size: " + this.photosQueue.size());
		}
	}
	
	private File getTargetFile(Photo photo) throws IOException {
		
		File targetDir = getTargetDirectory(photo.getFileOriginalDateCalendar());
		
		return createFile(targetDir, 1, photo);
	}
	
	
	private File createFile(File folder, int fileIndex, Photo photo) throws IOException {
		
		File result = new File(folder, photo.getBaseFileName() + fileIndex + photo.getFileExtension());
		if (!result.exists()) {
			result.createNewFile();
			return result;
		}
			
		
		long existingFileChecksum = FileUtils.checksumCRC32(result);
		long targetChecksum = FileUtils.checksumCRC32(photo.getOriginalFile());
			
		if (existingFileChecksum == targetChecksum) {
			LOG.info("The file already exists in target directory and will not be copied again. File: " + photo.toString());
			
			return null;
		}
		
		return createFile(folder, fileIndex + 1, photo);
	}
	
	private File getTargetDirectory(Calendar photoDateCalendar) {
		
		StringBuilder dirNameBuilder = new StringBuilder();
		dirNameBuilder.append(photoDateCalendar.get(Calendar.YEAR));
		dirNameBuilder.append("-");
		int month = photoDateCalendar.get(Calendar.MONTH) + 1;
		dirNameBuilder.append(month < 10? "0" + month: month);

		File targetDirectory = new File(targetRootDirectory, dirNameBuilder.toString());
		if (!targetDirectory.exists()) {
			targetDirectory.mkdir();
		}
		return targetDirectory;
	}
	
}
